package cn.funeralobjects.project.sshd.config;

import cn.funeralobjects.project.sshd.config.properties.SshProperties;
import cn.funeralobjects.project.sshd.server.SshApplication;
import cn.funeralobjects.project.sshd.utils.PublicKeyUtils;
import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.common.AttributeRepository;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
import org.apache.sshd.common.forward.PortForwardingEventListener;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoConnector;
import org.apache.sshd.common.io.IoServiceEventListener;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.common.keyprovider.FileHostKeyCertificateProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.session.SessionHeartbeatController;
import org.apache.sshd.common.session.SessionListener;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.net.SshdSocketAddress;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.server.ServerBuilder;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
import org.apache.sshd.server.forward.StaticDecisionForwardingFilter;
import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.SessionFactory;
import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
import org.apache.sshd.server.shell.ProcessShellCommandFactory;
import org.apache.sshd.server.shell.ProcessShellFactory;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author FuneralObjects Create date: 2020/8/5 4:07 AM
 */
@Configuration
@EnableConfigurationProperties({
        SshProperties.class
})
public class SshConfiguration {
    private static final Logger log = LoggerFactory.getLogger(SshConfiguration.class);

    private final SshProperties sshProperties;

    @Autowired
    public SshConfiguration(SshProperties sshProperties) {
        this.sshProperties = sshProperties;
    }

    private static void configureAuthentication(SshServer sshServer, SshProperties sshProperties) {
        // sshServer.setPublickeyAuthenticator((username, publicKey, serverSession) -> {
        // /*
        // * try { PublicKey pub = PublicKeyUtils.readPubFile(new File(sshProperties.getPubLocation() + '/' + username + ".pub")); return
        // * pub.equals(publicKey); } catch (IOException e) { throw new RuntimeException(e); }
        // */
        // return true;
        // });
        // use file ~/.ssh/authorized_keys
        sshServer.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(false));

    }

    private static void configureKeyExchange(SshServer sshServer, SshProperties sshProperties) {
        // List<KeyExchangeFactory> kexList = BuiltinDHFactories.VALUES.stream().map(ClientBuilder.DH2KEX).collect(Collectors.toList());
        // sshServer.setKeyExchangeFactories(kexList);

        sshServer.setKeyExchangeFactories(Stream
                .of(BuiltinDHFactories.dhg1, BuiltinDHFactories.dhg14)
                .map(ServerBuilder.DH2KEX)
                .collect(Collectors.toList()));
    }

    private static void configureKeyPairProvider(SshServer server, SshProperties sshProperties) {
        String hostKeyType = AbstractGeneratorHostKeyProvider.SSH_RSA;
        KeyPairProvider provider = null;
        try {
            provider = PublicKeyUtils.resolveServerKeys(hostKeyType, 4, Collections.singletonList(
                    sshProperties.getHostKeyLocation()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        server.setKeyPairProvider(provider);
        server.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
    }

    private static void configureSubsystem(SshServer sshServer, SshProperties sshProperties) {

        String pathName = new File(sshProperties.getSharedLocation()).getAbsolutePath();
        if (OsUtils.isWin32()) {
            pathName = pathName.replaceAll("\\\\\\.\\\\", "/");
        } else {
            pathName = pathName.replaceAll("/\\./", "/");
        }
        sshServer.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get(pathName)));
        sshServer.setCommandFactory(ProcessShellCommandFactory.INSTANCE);
        sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));

    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @PostConstruct
    private void init() throws IOException {
        new File(sshProperties.getPubLocation()).mkdirs();
        new File(sshProperties.getSharedLocation()).mkdirs();
        new File(sshProperties.getHostKeyLocation()).createNewFile();
    }

    @Bean
    public SshServer sshServer() {
        SshServer sshServer = SshServer.setUpDefaultServer();
        sshServer.setPort(sshProperties.getPort());
        // 新加
        // sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
        sshServer.setPasswordAuthenticator((username, password, session) -> {
            // TODO 自定义逻辑
            return true;
        });
        // sshServer.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);

        // sshServer.setShellFactory(new ProcessShellFactory("/bin/bash", "-i"));

        // 加上这个配置,终端访问的时候就要输入密码
        // sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(false));
        // 端口转发
        sshServer.setForwardingFilter(new AcceptAllForwardingFilter());
        // 客户端心跳 30分钟
        sshServer.setSessionHeartbeat(SessionHeartbeatController.HeartbeatType.IGNORE, Duration.ofMinutes(30L));
        // 监听器
        sshServer.setIoServiceEventListener(new IoServiceEventListener() {
            @Override
            public void connectionEstablished(IoConnector connector, SocketAddress local, AttributeRepository context, SocketAddress remote)
                    throws IOException {
                log.info("local:{},remote:{} connectionEstablished.", local, remote);
            }

            @Override
            public void abortEstablishedConnection(IoConnector connector, SocketAddress local, AttributeRepository context, SocketAddress remote,
                    Throwable reason) throws IOException {
                log.warn("local:{},remote:{} abortEstablishedConnection, reason is", local, remote, reason);
            }

            @Override
            public void connectionAccepted(IoAcceptor acceptor, SocketAddress local, SocketAddress remote, SocketAddress service)
                    throws IOException {
                log.info("local:{},remote:{},service{} connectionAccepted.", local, remote, service);
            }

            @Override
            public void abortAcceptedConnection(IoAcceptor acceptor, SocketAddress local, SocketAddress remote, SocketAddress service,
                    Throwable reason) throws IOException {
                log.warn("local:{},remote:{},service:{} abortEstablishedConnection, reason is", local, remote, service, reason);
            }
        });
        // session 监听器
        sshServer.addSessionListener(new SessionListener() {
            @Override
            public void sessionEstablished(Session session) {
                // examine the peer address or the connection context and set some attributes
                log.info("session established.");
            }

            @Override
            public void sessionCreated(Session session) {
                // session.setAttribute(STR_KEY, "Some string value");
                // session.setAttribute(LONG_KEY, 3777347L);
                // ...etc...
                log.info("session created.");
            }

            @Override
            public void sessionClosed(Session session) {
                // String str = session.getAttribute(STR_KEY);
                // Long l = session.getAttribute(LONG_KEY);
                // ... do something with the retrieved attributes ...
                log.info("session closed.");
            }
        });
        sshServer.addPortForwardingEventListener(new PortForwardingEventListener() {
            @Override
            public void establishingExplicitTunnel(Session session, SshdSocketAddress local, SshdSocketAddress remote, boolean localForwarding)
                    throws IOException {
                log.info("establishingExplicitTunnel");
            }

            @Override
            public void establishedExplicitTunnel(Session session, SshdSocketAddress local, SshdSocketAddress remote, boolean localForwarding,
                    SshdSocketAddress boundAddress, Throwable reason) throws IOException {
                log.info("establishedExplicitTunnel");
            }

            @Override
            public void tearingDownExplicitTunnel(Session session, SshdSocketAddress address, boolean localForwarding,
                    SshdSocketAddress remoteAddress) throws IOException {
                log.info("tearingDownExplicitTunnel");
            }

            @Override
            public void tornDownExplicitTunnel(Session session, SshdSocketAddress address, boolean localForwarding, SshdSocketAddress remoteAddress,
                    Throwable reason) throws IOException {
                log.info("tornDownExplicitTunnel");
            }

            @Override
            public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
                log.info("establishingDynamicTunnel");
            }

            @Override
            public void establishedDynamicTunnel(Session session, SshdSocketAddress local, SshdSocketAddress boundAddress, Throwable reason)
                    throws IOException {
                log.info("establishedDynamicTunnel");
            }

            @Override
            public void tearingDownDynamicTunnel(Session session, SshdSocketAddress address) throws IOException {
                log.info("tearingDownDynamicTunnel");
            }

            @Override
            public void tornDownDynamicTunnel(Session session, SshdSocketAddress address, Throwable reason) throws IOException {
                log.info("tornDownDynamicTunnel");
            }
        });
        // 忘记加这个了 TODO . Note that the ShellFactory is not required. If none is configured, any request for an interactive shell will be denied to
        // clients.
        // used for OpenSSH public-key certificate authentication system as defined in this document
        // sshServer.setHostKeyCertificateProvider(new FileHostKeyCertificateProvider(Paths.get(" ~/.ssh/authorized_keys")));
        sshServer.setShellFactory(new InteractiveProcessShellFactory());
        // sshServer.setShellFactory(new ProcessShellFactory("/bin/sh", "-i", "-l"));
        // 新加结束
        sshServer.setSessionFactory(new SessionFactory(sshServer));
        return sshServer;
    }

    @Bean
    public SshApplication sshApplication(SshServer sshServer) {
        SshApplication sshApplication = new SshApplication();
        sshApplication.setSshServer(sshServer);
        configureAuthentication(sshServer, sshProperties);
        configureKeyPairProvider(sshServer, sshProperties);
        configureKeyExchange(sshServer, sshProperties);
        configureSubsystem(sshServer, sshProperties);
        return sshApplication;
    }

}
